/*
 * Driver -registers- for Winbond USB2WLAN b/g WiFi device on Linux 2.6
 * Many portions based on original GPLed driver from Winbond.
 *
 * Written by Ferenc Csicsova & Karoly Kasza
 * Based on RTL8187 driver in kernel.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/usb.h>
#include <net/mac80211.h>

#include "wbusb.h"

//Forward declarations
void wbusb_reg_EP0VM(struct wbusb_priv *priv);
void wbusb_reg_update(struct wbusb_priv *priv, u16 reg, u32 regval);

//When a fill_control_urb completes
void wbusb_EP0VM_complete(struct urb *purb)
{
	PREG_QUEUE prq;
	struct wbusb_priv *priv = (struct wbusb_priv*)purb->context;

	priv->regs.EP0vm_state = VM_COMPLETED;

	DEBUG_VM0("EP0VM_complete")

	spin_lock_irq(&priv->regs.spinlock);
	prq = priv->regs.pRegFirst;
	if (prq == priv->regs.pRegLast) priv->regs.pRegLast = NULL;
	priv->regs.pRegFirst = priv->regs.pRegFirst->Next;
	spin_unlock_irq(&priv->regs.spinlock);

	if (purb->status) {
		DEBUG("VM0: Some funny things happened...")
		priv->regs.EP0vm_state = VM_STOP;
	} else {
		//2DO-S
		//register settings, priv... etc
		if (prq->DIRECT==2) { //2, burst
			DEBUG_REG("EP0VM write_burst (complete): [%04x]", prq->INDEX)
		}
		if (prq->DIRECT==1) { //1, write
			DEBUG_REG("EP0VM write: [%04x] (%x)", prq->INDEX, cpu_to_le32(prq->VALUE))
			wbusb_reg_update(priv,prq->INDEX, cpu_to_le32(prq->VALUE));
		}
		if (prq->DIRECT==0) { //0, read
			DEBUG_REG("EP0VM read: [%04x] (%x)", prq->INDEX, cpu_to_le32(*(unsigned *)prq->pBuffer))
			wbusb_reg_update(priv,prq->INDEX, cpu_to_le32(*(unsigned *)prq->pBuffer));
		}
		wbusb_reg_EP0VM(priv);
	}

	kfree(prq);
	usb_free_urb(purb);

	return;
}

//If we got something to tell through USB
//MANY from old code
void wbusb_reg_EP0VM(struct wbusb_priv *priv)
{
	PREG_QUEUE prq;
	struct usb_ctrlrequest *dr;
	struct urb *purb;
	u32* pBuffer;
	int ret;

	DEBUG_VM0("reg_EP0VM call")
	if (priv->regs.sync_io) goto cleanup;

	spin_lock_irq(&priv->regs.spinlock);
	prq = priv->regs.pRegFirst;
	spin_unlock_irq(&priv->regs.spinlock);

	if (!prq) goto cleanup;
	dr = prq->pUsbReq;
	purb = prq->purb;
	pBuffer = prq->pBuffer;

	if (prq->DIRECT == 1)
		pBuffer = &prq->VALUE;

	usb_fill_control_urb(purb, priv->udev, REG_DIRECTION(priv->udev,prq),
			     (unsigned char*)dr, pBuffer,
			     cpu_to_le16(dr->wLength), wbusb_EP0VM_complete,
			     (void *)priv);

	priv->regs.EP0vm_state=VM_RUNNING;
	ret = usb_submit_urb(purb, GFP_ATOMIC);
	if (ret<0) {
		printk(KERN_ERR "wbusb: reg_EP0vm USB_SUBMIT_URB failed!\n");
		goto cleanup;
	}

	return;

cleanup:
	DEBUG_VM0("reg_EP0VM cleanup")
	priv->regs.EP0vm_state = VM_STOP;
	priv->regs.EP0vm_count--;
	return;
}

//Start communicating with USB device
void wbusb_reg_EP0VM_start(struct wbusb_priv *priv)
{
	DEBUG_VM0("reg_EP0VM_start call")
	if (priv->regs.EP0vm_count==0) {
		priv->regs.EP0vm_count++;
		priv->regs.EP0vm_state=VM_RUNNING;
		wbusb_reg_EP0VM(priv);
	}
	return;
}

//Update registers
void wbusb_reg_update(struct wbusb_priv *priv, u16 reg, u32 regval)
{
	DEBUG_REG("reg_update [%04x] (%x)", reg, regval)
}

//Read register - nearly the same as the wcbv one
int wbusb_reg_read(struct wbusb_priv *priv, u16 reg, u32 *regval)
{
	PREG_QUEUE prq = NULL;
	u16 urbsize;
	struct urb *purb;
	struct usb_ctrlrequest *dr;

	urbsize = sizeof(REG_QUEUE) + sizeof(struct usb_ctrlrequest);
	prq = kmalloc(urbsize, GFP_ATOMIC);
	purb = usb_alloc_urb(0, GFP_ATOMIC);

	if ((!purb) || (!prq)) {
		if (purb) usb_free_urb(purb);
		kfree(prq);
		printk(KERN_ERR "wbusb: Cannot read register %0X\n", reg);
		return 1;
	}

	prq->DIRECT = 0;
	prq->INDEX = reg;
	prq->pBuffer = regval;

	dr = (struct usb_ctrlrequest *)((u8 *)prq+sizeof(REG_QUEUE));

	dr->bRequestType = USB_TYPE_VENDOR|USB_RECIP_DEVICE|USB_DIR_IN;
	dr->bRequest = 0x01; //Defined request code, burst mode

	dr->wValue = 0; //cpu_to_le16(0x0)
	dr->wIndex = cpu_to_le16(reg);
	dr->wLength = cpu_to_le16(4);

	prq->Next = NULL;
	prq->pUsbReq = dr;
	prq->purb = purb;

	spin_lock_irq(&priv->regs.spinlock);
	if (priv->regs.pRegFirst == NULL)
		priv->regs.pRegFirst = prq;
	else
		priv->regs.pRegLast->Next = prq;
	priv->regs.pRegLast = prq;
	spin_unlock_irq(&priv->regs.spinlock);

	wbusb_reg_EP0VM_start(priv);
	return 0;
}

//Read register sync
int wbusb_reg_read_sync(struct wbusb_priv *priv, u16 reg, u32 *regval)
{
	int ret;
	u32 *regswap = regval;

	priv->regs.sync_io=1;
	while (priv->regs.EP0vm_state!=VM_STOP)
		OS_SLEEP(10000);
	priv->regs.EP0vm_state=VM_RUNNING;

	ret = usb_control_msg(priv->udev,
			      usb_rcvctrlpipe(priv->udev, 0),
			      0x01, USB_TYPE_VENDOR|USB_RECIP_DEVICE|USB_DIR_IN,
			      0x0, reg, regswap, 4, HZ*100);
	if (ret < 0) {
		printk(KERN_ERR "wbusb: Cannot read register %0X\n", reg);
		return 1;
	}
	//Needed if this device is not used on a Little Endian computer
	*regval = cpu_to_le32(*regswap);

	priv->regs.EP0vm_state=VM_STOP;
	priv->regs.sync_io=0;

	DEBUG_REG("reg_read_sync: [%04x] (%x)", reg, (unsigned)*regval)

	wbusb_reg_update(priv,reg,(unsigned)*regval);
	wbusb_reg_EP0VM_start(priv);

	return 0;
}

//Write register sync
int wbusb_reg_write_sync(struct wbusb_priv *priv, u16 reg, u32 regval)
{
	int ret;

	priv->regs.sync_io=1;
	while (priv->regs.EP0vm_state!=VM_STOP)
		OS_SLEEP(10000);
	priv->regs.EP0vm_state=VM_RUNNING;

	regval = cpu_to_le32(regval); //Needed if not used on a Little Endian Computer
	ret = usb_control_msg(priv->udev,
			      usb_sndctrlpipe(priv->udev, 0),
			      0x03, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
			      0x0, reg, &regval, 4, HZ*100);

	priv->regs.EP0vm_state=VM_STOP;
	priv->regs.sync_io=0;

	if (ret < 0) {
		printk(KERN_ERR "wbusb: Cannot write register %0X\n", reg);
		return 1;
	}

	DEBUG_REG("reg_write_sync: [%04x] (%x)", reg, regval)

	wbusb_reg_update(priv,reg,regval);
	wbusb_reg_EP0VM_start(priv);

	return 0;
}

//Write register - use the wcbv one, with 0 lenght of burst
int wbusb_reg_write(struct wbusb_priv *priv, u16 reg, u32 regval)
{
	return wbusb_reg_write_wcbv(priv, reg, regval, NULL, 0);
}

//Write register WithCallBackValue
int wbusb_reg_write_wcbv(struct wbusb_priv *priv, u16 reg, u32 regval, s8 *pval, s8 len)
{
	PREG_QUEUE prq = NULL;
	u16 urbsize;
	struct urb *purb;
	struct usb_ctrlrequest *dr;

	urbsize = sizeof(REG_QUEUE) + sizeof(struct usb_ctrlrequest);
	prq = kmalloc(urbsize, GFP_ATOMIC);
	purb = usb_alloc_urb(0, GFP_ATOMIC);

	if ((!purb) || (!prq)) {
		if (purb) usb_free_urb(purb);
		kfree(prq);
		printk(KERN_ERR "wbusb: Cannot write (wcbv) register %0X\n", reg);
		return 1;
	}

	prq->DIRECT = 1;
	prq->INDEX = reg;
	prq->VALUE = cpu_to_le32(regval);

	//WithCallBackValue functionality
	if (len>0) {
		memcpy(prq->RESERVED, pval, len);
		prq->RESERVED_VALID=1;
	}
	else prq->RESERVED_VALID=0;

	dr = (struct usb_ctrlrequest *)((u8 *)prq+sizeof(REG_QUEUE));

	dr->bRequestType = USB_TYPE_VENDOR|USB_RECIP_DEVICE|USB_DIR_OUT;
	dr->bRequest = 0x03; //Defined request code, burst mode

	dr->wValue = 0; //cpu_to_le16(0x0)
	dr->wIndex = cpu_to_le16(reg);
	dr->wLength = cpu_to_le16(4);

	prq->Next = NULL;
	prq->pUsbReq = dr;
	prq->purb = purb;

	spin_lock_irq(&priv->regs.spinlock);
	if (priv->regs.pRegFirst == NULL)
		priv->regs.pRegFirst = prq;
	else
		priv->regs.pRegLast->Next = prq;
	priv->regs.pRegLast = prq;
	spin_unlock_irq(&priv->regs.spinlock);

	wbusb_reg_EP0VM_start(priv);
	return 0;
}

//Write register BURST
int wbusb_reg_write_burst(struct wbusb_priv *priv, u16 reg, u32 *pregdata, u8 numdata, u8 flag)
{
	PREG_QUEUE prq = NULL;
	u16 urbsize;
	struct urb *purb;
	struct usb_ctrlrequest *dr;
	u16 i;

	urbsize = sizeof(REG_QUEUE) + numdata + sizeof(struct usb_ctrlrequest);
	prq = kmalloc(urbsize, GFP_ATOMIC);
	purb = usb_alloc_urb(0, GFP_ATOMIC);

	if ((!purb) || (!prq)) {
		if (purb) usb_free_urb(purb);
		kfree(prq);
		printk(KERN_ERR "wbusb: Cannot write (burst) register %0X\n", reg);
		return 1;
	}

	prq->DIRECT = 2; //Burst
	prq->INDEX = reg;
	prq->pBuffer = (u32*)((u8*)prq + sizeof(REG_QUEUE));
	memcpy(prq->pBuffer, pregdata, numdata*4);

	DEBUG_REG("burst write [%0X] (flag %d)", reg, flag);
	for(i=0; i<numdata; i++) {
		prq->pBuffer[i] = cpu_to_le32(prq->pBuffer[i]);
		DEBUG(" > %0X",prq->pBuffer[i]);
	}

	dr = (struct usb_ctrlrequest *)((u8 *)prq+sizeof(REG_QUEUE));

	dr->bRequestType = USB_TYPE_VENDOR|USB_RECIP_DEVICE|USB_DIR_OUT;
	dr->bRequest = 0x04; //Defined request code, burst mode agaain

	dr->wValue = cpu_to_le16(flag);
	dr->wIndex = cpu_to_le16(reg);
	dr->wLength = cpu_to_le16(numdata*4);

	prq->Next = NULL;
	prq->pUsbReq = dr;
	prq->purb = purb;

	spin_lock_irq(&priv->regs.spinlock);
	if (priv->regs.pRegFirst == NULL)
		priv->regs.pRegFirst = prq;
	else
		priv->regs.pRegLast->Next = prq;
	priv->regs.pRegLast = prq;
	spin_unlock_irq(&priv->regs.spinlock);

	wbusb_reg_EP0VM_start(priv);
	return 0;
}
